---
title: Expo Fingerprint
sidebar_title: Fingerprint
description: A library to generate a fingerprint from a React Native project.
sourceCodeUrl: 'https://github.com/expo/expo/tree/sdk-53/packages/@expo/fingerprint'
packageName: '@expo/fingerprint'
iconUrl: '/static/images/packages/expo-fingerprint.png'
platforms: ['node']
isNew: true
---

import APISection from '~/components/plugins/APISection';
import { Collapsible } from '~/ui/components/Collapsible';
import { Terminal } from '~/ui/components/Snippet';
import { CODE } from '~/ui/components/Text';

`@expo/fingerprint` provides an API to generate a fingerprint (hash) of your project for use in determining compatibility between the native layer and JavaScript layer of your app. The hash calculation is configurable, but is by default derived from hashing app dependencies, custom native code, native project files, and configuration.

## Installation

`@expo/fingerprint` is included with [`expo`](expo.mdx) and [`expo-updates`](updates.mdx) by default.

If you wish to use `@expo/fingerprint` as a standalone package, you can install it by running the command:

<Terminal cmd={['$ npx expo install @expo/fingerprint']} />

## CLI Usage

<Terminal cmd={['$ npx @expo/fingerprint --help']} />

## Configuration

`@expo/fingerprint` provides defaults that should work for most projects, but also provides a few ways to configure the fingerprinting process to better fit your app structure and workflow.

### .fingerprintignore

Placed in your project root, **.fingerprintignore** is a [**.gitignore**](https://git-scm.com/docs/gitignore#_pattern_format)-like ignore mechanism used to exclude files from hash calculation. All pattern paths are relative to the project root. It behaves similarly but instead uses `minimatch` for pattern matching which has some [limitations](#limitations) (see documentation for `ignorePaths` under [Options](#options)).

Here is an example **.fingerprintignore** configuration:

```ignore .fingerprintignore
# Ignore the entire android directory
android/**/*

# Ignore the entire ios directory but still keep ios/Podfile and ios/Podfile.lock
ios/**/*
!ios/Podfile
!ios/Podfile.lock

# Ignore specific package in node_modules
node_modules/some-package/**/*

# Same as above but having broader scope because packages may be nested
**/node_modules/some-package/**/*
```

### fingerprint.config.js

Placed in your project root, **fingerprint.config.js** allows you to specify custom hash calculation configuration beyond what is configurable in the **.fingerprintignore**. For supported configurations, see [Config](#config) and [`SourceSkips`](#sourceskips).

Below is an example **fingerprint.config.js** configuration, assuming you have `@expo/fingerprint` installed as a direct dependency:

```js fingerprint.config.js
/** @type {import('@expo/fingerprint').Config} */
const config = {
  sourceSkips: [
    'ExpoConfigRuntimeVersionIfString',
    'ExpoConfigVersions',
    'PackageJsonAndroidAndIosScriptsIfNotContainRun',
  ],
};
module.exports = config;
```

If you are using `@expo/fingerprint` through `expo` (where `@expo/fingerprint` is installed as a transitive dependency), you can import fingerprint from `expo/fingerprint`:

```js
/** @type {import('expo/fingerprint').Config} */
```

<Collapsible summary="Advanced: Customize sources before fingerprint hashing">

In some cases, you may want to customize the sources before fingerprinting. For example:

- You want to remove sensitive data from the app config.
- You want to stabilize dynamic values in the app config.
- You want to transform file hashes to stable values.

To do this, you can use the `fileHookTransform` option in the **fingerprint.config.js** file to transform the sources before hashing. Learn more about the [`fileHookTransform` option](#filehooktransformfunctionsource-chunk-isendoffile-encoding).

```js fingerprint.config.js
const assert = require('node:assert');

const fileChunkMap = {};

/** @type {import('@expo/fingerprint').Config} */
const config = {
  fileHookTransform: (source, chunk, isEndOfFile, encoding) => {
    // Remove the "updates" section from the app config
    if (source.type === 'contents' && source.id === 'expoConfig') {
      assert(isEndOfFile, 'contents source is expected to have single chunk.');
      const config = JSON.parse(chunk);
      delete config.updates;
      return JSON.stringify(config);
    }

    // Transform content sources to an empty string
    if (source.type === 'contents' && source.id === 'packageJson:scripts') {
      return '';
    }

    // Transform a file source by replacing dynamic values
    if (source.type === 'file' && source.filePath === 'eas.json') {
      return chunk.toString().replace(/MyApp-Dev/g, 'MyApp');
    }

    // Transform a large file that is processed in multiple chunks
    // To get the full file, buffer all chunks and return them all at once
    if (source.type === 'file' && source.filePath === 'assets/large-image.jpg') {
      let receivedBuffer = fileChunkMap[source.filePath] ?? Buffer.alloc(0);
      if (chunk != null) {
        const buffer = typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk;
        receivedBuffer = Buffer.concat([receivedBuffer, buffer]);
        fileChunkMap[source.filePath] = receivedBuffer;
      }
      if (!isEndOfFile) {
        return null;
      }
      fileChunkMap[source.filePath] = null;
      // The full payload is available here and you can transform it as needed.
      receivedBuffer = receivedBuffer.toString().replace(/SensitiveData/g, 'StableData');
      return receivedBuffer;
    }

    // For other sources, just return the chunk
    return chunk;
  },
};

module.exports = config;
```

</Collapsible>

## Limitations

<Collapsible summary={<>Limited support for <CODE>@expo/config-plugins</CODE> raw functions</>}>

When using config plugins with raw functions, it's essential to be aware of certain limitations, particularly in the context of fingerprinting. The library makes a best effort to generate fingerprints for changes made through config plugins; however, raw functions pose specific challenges. Raw functions are not serializable as fingerprints, which means they cannot be directly used for generating unique hashes.

To work around this limitation, the library employs one of the following strategies to create serializable fingerprints for raw functions:

1. Using `Function.name`: The library utilizes the `Function.name` property if available for named raw functions. This property provides a recognizable name for the function, which can be used as a fingerprint property.

2. Using `withAnonymous`: For anonymous raw functions without a `Function.name`, the library resorts to using `withAnonymous` as the fingerprint property. This is a generic identifier for anonymous functions.

Here's an example to illustrate a case in which the library will use [`withMyPlugin`, `withAnonymous`] as plugin properties for fingerprint hashing:

{/* prettier-ignore */}
```js app.config.js
const { withInfoPlist } = require('expo/config-plugins');

const withMyPlugin = (config) => {
  return withInfoPlist(config, (config) => {
    config.modResults.NSLocationWhenInUseUsageDescription = 'Allow $(PRODUCT_NAME) to use your location';
    return config;
  });
};

export default ({ config }) => {
  config.plugins ||= [];
  config.plugins.push(withMyPlugin);
  config.plugins.push((config) => config);
  return config;
};
```

It's important to note that due to this design, if you make changes to the implementation of raw config plugins functions, such as altering the **Info.plist** value within `withMyPlugin`, the fingerprint will still generate the same hash value. To ensure unique fingerprints when modifying config plugins implementations, consider the following options:

- Avoid Anonymous Functions: Avoid using anonymous raw config plugins functions. Instead, use named functions whenever possible, and ensure that their names remain consistent as long as the implementation changes.

- Use Local config plugins: Alternatively, you can create local config plugins as separate modules, each with its own export. This approach allows you to specify a different function name when making changes to the config plugins implementations.

Here's an example of using a local config plugin:

```js ./plugins/withMyPlugin.js
const { withInfoPlist } = require('expo/config-plugins');

const withMyPlugin = config => {
  return withInfoPlist(config, config => {
    config.modResults.NSLocationWhenInUseUsageDescription =
      'Allow $(PRODUCT_NAME) to use your location';
    return config;
  });
};

module.exports = withMyPlugin;
```

{/* prettier-ignore */}
```json app.json
{
  "expo": {
    /* @hide ... */ /* @end */
    "plugins": "./plugins/withMyPlugin"
  }
}
```

By following these guidelines, you can effectively manage changes to config plugins and ensure that fingerprinting remains consistent and reliable.

</Collapsible>

## API

```ts
import * as Fingerprint from '@expo/fingerprint';
```

<APISection packageName="@expo/fingerprint" apiName="Fingerprint" />
